/*
 * nbd.h
 *
 * Copyright (C) 2019 Alex A. <coderain@sdf.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _NBD_H_
#define _NBD_H_

#include <stdint.h>
#include <stdbool.h>
#include "hashtab.h"

#define NBD_MAGIC              0x4E42444D41474943ULL
#define NBD_OLDSTYLE_MAGIC     0x0000420281861253ULL
#define NBD_NEWSTYLE_MAGIC     0x49484156454F5054ULL
#define NBD_OPT_REPLY_MAGIC    0x0003E889045565A9ULL
#define NBD_REQUEST_MAGIC      0x25609513UL
#define NBD_SIMPLE_REPLY_MAGIC 0x67446698UL

/* Connection Flags */
#define NBD_FLAG_HAS_FLAGS         (1 << 0)
#define NBD_FLAG_READ_ONLY         (1 << 1)
#define NBD_FLAG_SEND_FLUSH        (1 << 2)
#define NBD_FLAG_SEND_FUA          (1 << 3)
#define NBD_FLAG_ROTATIONAL        (1 << 4)
#define NBD_FLAG_SEND_TRIM         (1 << 5)
#define NBD_FLAG_SEND_WRITE_ZEROES (1 << 6)
#define NBD_FLAG_SEND_DF           (1 << 7)
#define NBD_FLAG_CAN_MULTI_CONN    (1 << 8)
#define NBD_FLAG_SEND_RESIZE       (1 << 9)
#define NBD_FLAG_SEND_CACHE        (1 << 10)

/* Handshake Flags */
#define NBD_FLAG_FIXED_NEWSTYLE (1 << 0)
#define NBD_FLAG_NO_ZEROES      (1 << 1)

/* Handshake Client Flags */
#define NBD_FLAG_C_FIXED_NEWSTYLE (1 << 0)
#define NBD_FLAG_C_NO_ZEROES      (1 << 1)

typedef enum
{
    NBD_CMD_READ = 0,
    NBD_CMD_WRITE,
    NBD_CMD_DISC,
    NBD_CMD_FLUSH,
    NBD_CMD_TRIM,
    NBD_CMD_CACHE,
    NBD_CMD_WRITE_ZEROES,
    NBD_CMD_BLOCK_STATUS,
    NBD_CMD_RESIZE,
} nbd_cmd_t;

typedef enum
{
    NBD_OPT_EXPORT_NAME = 1,
    NBD_OPT_ABORT,
    NBD_OPT_LIST,
    NBD_OPT_PEEK_EXPORT,
    NBD_OPT_STARTTLS,
    NBD_OPT_INFO,
    NBD_OPT_GO,
} nbd_opt_t;

typedef enum
{
    NBD_REP_ACK = 1,
    NBD_REP_SERVER,
    NBD_REP_INFO,
    NBD_REP_META_CONTEXT,

    NBD_REP_ERR_UNSUP = 0x80000001,
    NBD_REP_ERR_POLICY,
    NBD_REP_ERR_INVALID,
    NBD_REP_ERR_PLATFORM,
    NBD_REP_ERR_TLS_REQD,
    NBD_REP_ERR_UNKNOWN,
    NBD_REP_ERR_SHUTDOWN,
    NBD_REP_ERR_BLOCK_SIZE_REQD,
    NBD_REP_ERR_TOO_BIG,
} nbd_rep_t;

typedef enum
{
    NBD_INFO_EXPORT = 0,
    NBD_INFO_NAME,
    NBD_INFO_DESCRIPTION,
    NBD_INFO_BLOCK_SIZE,
} nbd_info_t;

typedef struct
{
    int sockfd;
    uint16_t flags;
    pthread_t thread;
    pthread_mutex_t mutex;
    uint64_t next_txid;
    hash_table_t tx_hash_table;
} nbd_conn_t;

typedef struct
{
    uint64_t image_size;
} nbd_image_t;

typedef int nbd_status_t;

typedef struct
{
    nbd_conn_t *conn;
    uint64_t id;
    nbd_cmd_t command;
    uint32_t length;
    nbd_status_t error_code;
    uint8_t complete;
    pthread_cond_t cond;
    uint8_t *reply;
} nbd_transaction_t;

nbd_status_t nbd_read(nbd_conn_t *conn, uint8_t *buffer, uint64_t offset, uint32_t length);
nbd_status_t nbd_write(nbd_conn_t *conn, const uint8_t *buffer, uint64_t offset, uint32_t length);
nbd_status_t nbd_flush(nbd_conn_t *conn);
bool nbd_connect_tcp(const char *host, const char *port, nbd_image_t *image, nbd_conn_t *conn);
bool nbd_connect_unix(const char *path, nbd_image_t *image, nbd_conn_t *conn);
bool nbd_is_connected(const nbd_conn_t *conn);
void nbd_disconnect(nbd_conn_t *conn);
void nbd_init(nbd_conn_t *conn);

#endif // NBD_H_
